#include "../header/fudpSender.hpp"

FUDP_Sender::FUDP_Sender(int _socket_id, struct sockaddr_in _dst_address, const char* filename){
    MLOG("init sender");
    socket_id = _socket_id;
    this->dst_address = _dst_address;
    
    this->srcName = string(filename);
    this->sendBase=0;
    this->nextSeqNum=0;
    this->sendBaseIndex=0;
    
    memset(window_size,0,sizeof(window_size));
}

// send and return result
bool FUDP_Sender::send(){
    MLOG("into send");
    msgstream filestream(msgstream::READ,this->srcName.c_str());
    int retransmit_pkt_count=0;
    MLOG("init Send thread");
    int real_size=0;
    char pieceBuffer[msg_onePieceSize];
    // the server's window left;
    int advise_window_length = sender_window_size;
    // the largest number of unack pkt send
    int window_length = 1;
    MLOG("TOTAL PKT:"<<filestream.fileSize);
    int over=1;
    float percentage=0.001f;
    int dumplicateSendCount=0;
    int ERRORpktCOUNT = 0;

    unsigned long ack_data_size = 0;

    while(over){
        // send some data,Although read from file will make it slow,
        // but for simple to program, just do it.
        if(window_length==1)
        {
            dumplicateSendCount++;
            if(dumplicateSendCount>=6){
                // some error happened, but it seems just split it can fix it
                int index = (sendBaseIndex + nextSeqNum - sendBase)%sender_window_size;
                MLOG("ERROR: send to much the same");
                ERRORpktCOUNT++;
                MLOG("ERROR piece size:"<<window_size[index]);
                window_size[index] = header_size+msg_onePieceSize;
                MLOG("INDEX:"<<index);

                //发送数据
                Datagram data(window[index]);
                data.getHeader().dataLength/=2;
                int temp_size =filestream.setPosition(ack_data_size+data.getHeader().dataLength);
                temp_size=data.get(window[index]);

                //just reset all
                memset(window_size,0,sizeof(window_size));
                window_size[index] = temp_size;

                dumplicateSendCount=4;
            }
        } else {
            dumplicateSendCount=0;
        }

        while(nextSeqNum-sendBase<window_length){
            int current_index_in_Window = (sendBaseIndex + nextSeqNum - sendBase)%sender_window_size;
            if(window_size[current_index_in_Window]<=0){
                real_size = filestream.readPiece(pieceBuffer);
                if(real_size<=0){
                    over = 0;
                    break;
                }

                Datagram data= Datagram(type_data);
                data.fillData(pieceBuffer,real_size,nextSeqNum);
                window_size[current_index_in_Window] = data.get(window[current_index_in_Window]);
            }
            // MLOG("SEQ:"<<this->nextSeqNum);

            int response=0;
            if(sendto(socket_id, window[current_index_in_Window], 
                    window_size[current_index_in_Window],0,
                    (struct sockaddr*)&dst_address,sizeof(dst_address)) < 0) { 
                MLOG("Send File Failed in pkt:"<<nextSeqNum); 
                exit(1);
            } else {
                datagram_header dheader(window[current_index_in_Window]);
                nextSeqNum++;
            }
        }

        char tempBuffer[max_datagram_size]={0};
        
        int dumplicate_ack_count=0;
        // recive some pkt ack, temp to use the socket's timer
        // Just end when some time no ack recive rightly
        // or  3 dumplicate ack
        while(sendBase != this->nextSeqNum 
                && dumplicate_ack_count<3
                &&(real_size =recvfrom(socket_id, tempBuffer, max_datagram_size,0,NULL, NULL))>0){
            datagram_header dataHeader(tempBuffer);
            // MLOG("get ACK:"<<dataHeader.seqNum);
            if(dataHeader.seqNum+1>sendBase){
                // got real ack
                sendBaseIndex = (sendBaseIndex + dataHeader.seqNum+1 - sendBase)%sender_window_size;
                for(int c1=0;c1<dataHeader.seqNum+1 - sendBase;c1++){
                    if(window_size[(sendBase+c1)%sender_window_size]!=0){
                        ack_data_size+=window_size[(sendBase+c1)%sender_window_size]-header_size;
                        window_size[(sendBase+c1)%sender_window_size]=0;
                    }
                }
                sendBase = dataHeader.seqNum+1;
                advise_window_length = dataHeader.rcv_window_size;
            } else {
                dumplicate_ack_count++;
            }
        }
        // resize the buffer
        if(this->nextSeqNum == sendBase) {
            // no pkt lost
            window_length = (window_length+1)<=sender_window_size?window_length+1:window_length;
            if(window_length>reciver_window_size){
                // no over the advise
                window_length = reciver_window_size;
            }
        } else {
            // detected pkt lost
            // MLOG("LOST PKT");
            retransmit_pkt_count+= this->nextSeqNum - this->sendBase;
            window_length = (window_length/2)>0?window_length/2:1;
            this->nextSeqNum = sendBase;
        }

        if(ack_data_size*1.0f/filestream.fileSize>percentage){
            MLOG("FINISHED:"<<percentage);
            percentage = ack_data_size*1.0f/filestream.fileSize + 0.001;
        }
    }

    MLOG("ERROR pkt count:"<<ERRORpktCOUNT);
    MLOG("retransmit pkt count:"<<retransmit_pkt_count);
    MLOG("SEND SIZE:"<<ack_data_size/1024.0/1024);
    MLOG("sender finish jobs")
    return true;
}